#pragma once
#include "VulkanViewport.h"
#include "Runtime/Utils/SmartPtr.h"
#include "CGIVulkan.h"
#include "VulkanBarriers.h"
#include "VulkanCommandBuffer.h"
#include "VulkanContext.h"
#include "VulkanFence.h"
#include "../CGICommandList.h"

namespace Alice{
    CGIVulkanBackBuffer::CGIVulkanBackBuffer(VulkanDevice& Device, VulkanViewport* InViewport, EPixelFormat Format, uint32 SizeX, uint32 SizeY, ETextureCreateFlags UEFlags)
        :CGIVulkanTexture(Device,
            CGITextureCreateDesc::Create2D(TEXT("FVulkanBackBuffer"), SizeX, SizeY, Format).SetFlags(UEFlags).DetermineInititialState(),
            VK_NULL_HANDLE, {}),
        mViewport(InViewport)
    {
        
    }
    uint32 CGIVulkanBackBuffer::OnGetBackBufferImage(CGICommandListImmediate&inCGICommandList)
    {
        VulkanCommandListContext& Context = (VulkanCommandListContext&)inCGICommandList.GetContext().GetLowestLevelContext();
        return AcquireBackBufferImage(Context);
    }
    uint32 CGIVulkanBackBuffer::AcquireBackBufferImage(VulkanCommandListContext& inVulkanCommandListContext)
    {
        if (mImage == VK_NULL_HANDLE)
        {
            uint32 Result = 0;
            if (mViewport->TryAcquireImageIndex())
            {
                int32 AcquiredImageIndex = mViewport->mAcquiredImageIndex;
                Result=AcquiredImageIndex;
                CGIVulkanCommandBufferManager*commandBufferManager=inVulkanCommandListContext.GetCommandBufferManager();
                VulkanCommandBuffer*commandBuffer=commandBufferManager->mActiveCmdBuffer;
                commandBuffer->AddWaitSemaphore(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, mViewport->mAcquiredSemaphore);
                
                //check(AcquiredImageIndex >= 0 && AcquiredImageIndex < Viewport->TextureViews.Num());
//
                //FVulkanView& ImageView = Viewport->TextureViews[AcquiredImageIndex];
//
                //Image = ImageView.GetTextureView().Image;
                //DefaultView = &ImageView;
                //PartialView = &ImageView;
//
                //FVulkanCommandBufferManager* CmdBufferManager = Context.GetCommandBufferManager();
                //FVulkanCmdBuffer* CmdBuffer = CmdBufferManager->GetActiveCmdBuffer();
                //check(!CmdBuffer->IsInsideRenderPass());
//
                //// right after acquiring image is in undefined state
                //FVulkanLayoutManager& LayoutMgr = CmdBuffer->GetLayoutManager();
                //const FVulkanImageLayout CustomLayout(VK_IMAGE_LAYOUT_UNDEFINED, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT);
                //LayoutMgr.SetFullLayout(Image, CustomLayout);
//
                //// Wait for semaphore signal before writing to backbuffer image
                //CmdBuffer->AddWaitSemaphore(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, Viewport->AcquiredSemaphore);
            }
            //else
            {
                // fallback to a 'dummy' backbuffer
                //check(Viewport->RenderingBackBuffer);
                //FVulkanView* DummyView = Viewport->RenderingBackBuffer->DefaultView;
                //Image = DummyView->GetTextureView().Image;
                //DefaultView = DummyView;
                //PartialView = DummyView;
            }
            return Result;
        }
        return 0;
    }
    VulkanViewport::VulkanViewport(VulkanDevice* InDevice, void* InWindowHandle, uint32 InSizeX, uint32 InSizeY, bool bInIsFullscreen, EPixelFormat InPreferredPixelFormat)
    : mDevice(InDevice)
    , SizeX(InSizeX)
    , SizeY(InSizeY)
    , bIsFullscreen(bInIsFullscreen)
    , PixelFormat(InPreferredPixelFormat)
    , mAcquiredImageIndex(-1)
    , mSwapChain(nullptr)
    , WindowHandle(InWindowHandle)
    , PresentCount(0)
    , bRenderOffscreen(false)
    , LockToVsync(1)
    , mAcquiredSemaphore(nullptr)
    {
        CreateSwapChain(nullptr);
    }
    void VulkanViewport::CreateSwapChain(VulkanSwapChainRecreateInfo* inVulkanSwapChainRecreateInfo)
    {
        uint32 desiredBackBuffersNum=3;
        std::vector<VkImage> images;
        mSwapChain=new VulkanSwapChain(CGIVulkan::Get().mVkInstance,*mDevice,WindowHandle,
            PixelFormat,SizeX,SizeY,bIsFullscreen,
            &desiredBackBuffersNum,images,LockToVsync,inVulkanSwapChainRecreateInfo);
        mBackBufferImages.resize(images.size());
        mRenderingDoneSemaphores.resize(images.size());
        mTextureViews.resize(images.size());

        VulkanCommandBuffer*commandBuffer=mDevice->GetImmediateContext().GetCommandBufferManager()->GetUploadCmdBuffer();
        const VkImageSubresourceRange Range = CGIVulkanPipelineBarrier::MakeSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT);
        const VulkanImageLayout InitialLayout(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT);
        VkClearColorValue ClearColor={};
        ClearColor.float32[0] = 0.1f;
        ClearColor.float32[1] = 0.4f;
        ClearColor.float32[2] = 0.6f;
        ClearColor.float32[3] = 1.0f;
        for (int32 Index = 0; Index < images.size(); ++Index)
        {
            uint32 ImageSizeX = SizeX;
            uint32 ImageSizeY = SizeY;
            
            mBackBufferImages[Index] = (CGIVulkanTexture*)CGIVulkan::Get().CGICreateTexture2DFromResource(PixelFormat, ImageSizeX, ImageSizeY, 1, 1, images[Index],
                ETextureCreateFlags::RenderTargetable | ETextureCreateFlags::Presentable).GetReference();
            const VkDescriptorType DescriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
            mTextureViews[Index]=(new CGIVulkanView(*mDevice, DescriptorType))->InitAsTextureView(
                images[Index], VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_ASPECT_COLOR_BIT, PixelFormat, VK_FORMAT_B8G8R8A8_UNORM, 0, 1, 0, 1, false);
            mBackBufferImages[Index]->mDefaultView=mTextureViews[Index];
            // Clear the swapchain to avoid a validation warning, and transition to PresentSrc
            {
                VulkanSetImageLayout(commandBuffer, images[Index], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, Range);
                vkCmdClearColorImage(commandBuffer->GetHandle(), images[Index], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &ClearColor, 1, &Range);
                //VulkanSetImageLayout(commandBuffer, images[Index], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, Range);
                VulkanSetImageLayout(commandBuffer, images[Index], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, InitialLayout.mMainLayout, Range);
                //commandBuffer->GetLayoutManager().SetFullLayout(images[Index], InitialLayout);
            }
            mRenderingDoneSemaphores[Index]=new VulkanSemaphore(*mDevice);
        }
        mDevice->GetImmediateContext().GetCommandBufferManager()->SubmitUploadCmdBuffer();
        mCGIBackBuffer=new CGIVulkanBackBuffer(*mDevice, this, PixelFormat, SizeX, SizeY,  ETextureCreateFlags::RenderTargetable | ETextureCreateFlags::ShaderResource | ETextureCreateFlags::ResolveTargetable);
    }
    SmartPtr<CGITexture> VulkanViewport::GetBackBuffer(CGICommandListImmediate& CGICmdList,uint32&inOutImageIndex)
    {
        uint32 imageIndex = mCGIBackBuffer->OnGetBackBufferImage(CGICmdList);
        return mBackBufferImages[imageIndex];
    }
    bool VulkanViewport::TryAcquireImageIndex()
    {
        if(mSwapChain!=NULL)
        {
            mAcquiredImageIndex = mSwapChain->AcquireImageIndex(&mAcquiredSemaphore);
            return true;
        }
        return false;
    }
    bool VulkanViewport::Present(VulkanCommandListContext* Context, VulkanCommandBuffer* CmdBuffer, VulkanQueue* inQueue, VulkanQueue* PresentQueue, bool bLockToVsync)
    {
        VulkanLayoutManager& LayoutManager = CmdBuffer->GetLayoutManager();
        const VulkanImageLayout* TrackedLayout = LayoutManager.GetFullLayout(mBackBufferImages[mAcquiredImageIndex]->mImage);

        VkImageLayout LastLayout = TrackedLayout ? TrackedLayout->mMainLayout : VK_IMAGE_LAYOUT_UNDEFINED;
        VulkanSetImageLayout(CmdBuffer, mBackBufferImages[mAcquiredImageIndex]->mImage, LastLayout, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, CGIVulkanPipelineBarrier::MakeSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT));

        //VulkanImageLayout * UndefinedLayout=new VulkanImageLayout(VK_IMAGE_LAYOUT_UNDEFINED, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT);
        //LayoutManager.SetFullLayout(mBackBufferImages[mAcquiredImageIndex]->mImage, *UndefinedLayout);
        CmdBuffer->End();
        VkSemaphore readyToPresent=mRenderingDoneSemaphores[mAcquiredImageIndex]->GetHandle();
        inQueue->Submit(CmdBuffer,1,&readyToPresent);
        
        uint32 imageIndex=mAcquiredImageIndex;
        VkPresentInfoKHR presentInfoKHR = {};
        presentInfoKHR.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
        presentInfoKHR.waitSemaphoreCount = 1;
        presentInfoKHR.pWaitSemaphores = &readyToPresent;
        presentInfoKHR.pSwapchains = &mSwapChain->mSwapChain;
        presentInfoKHR.swapchainCount = 1;
        presentInfoKHR.pImageIndices = &imageIndex;
        vkQueuePresentKHR(mDevice->mPresentQueue->mQueue, &presentInfoKHR);
        vkQueueWaitIdle(mDevice->mPresentQueue->mQueue);
        CmdBuffer->State=VulkanCommandBuffer::EState::NeedReset; 
        return true;
    }

    /*=============================================================================
     *	The following RHI functions must be called from the main thread.
     *=============================================================================*/
    SmartPtr<CGIViewport> CGIVulkan::CGICreateViewport(void* WindowHandle, uint32 SizeX, uint32 SizeY, bool bIsFullscreen, EPixelFormat PreferredPixelFormat)
    {
        return new VulkanViewport(mDevice, WindowHandle, SizeX, SizeY, bIsFullscreen, PreferredPixelFormat);
    }
}
